import 'dart:io';

import 'package:flutter/material.dart';

import 'scales.dart';
import 'themes.dart';

class AppOptions {
  AppOptions({
    this.theme,
    this.textScaleFactor,
    this.textDirection = TextDirection.ltr,
    this.timeDilation = 1.0,
    this.platform,
    this.proxy,
    this.showOffscreenLayersCheckerboard = false,
    this.showRasterCacheImagesCheckerboard = false,
    this.showPerformanceOverlay = false,
  });

  final AppTheme theme;
  final AppTextScaleValue textScaleFactor;
  final TextDirection textDirection;
  final double timeDilation;
  final TargetPlatform platform;

  /// See [HttpClient.findProxy]
  final String proxy;
  final bool showPerformanceOverlay;
  final bool showRasterCacheImagesCheckerboard;
  final bool showOffscreenLayersCheckerboard;

  AppOptions copyWith({
    AppTheme theme,
    AppTextScaleValue textScaleFactor,
    TextDirection textDirection,
    double timeDilation,
    TargetPlatform platform,
    String proxy,
    bool showPerformanceOverlay,
    bool showRasterCacheImagesCheckerboard,
    bool showOffscreenLayersCheckerboard,
  }) {
    return AppOptions(
      theme: theme ?? this.theme,
      textScaleFactor: textScaleFactor ?? this.textScaleFactor,
      textDirection: textDirection ?? this.textDirection,
      timeDilation: timeDilation ?? this.timeDilation,
      platform: platform ?? this.platform,
      proxy: proxy ?? this.proxy,
      showPerformanceOverlay:
          showPerformanceOverlay ?? this.showPerformanceOverlay,
      showOffscreenLayersCheckerboard: showOffscreenLayersCheckerboard ??
          this.showOffscreenLayersCheckerboard,
      showRasterCacheImagesCheckerboard: showRasterCacheImagesCheckerboard ??
          this.showRasterCacheImagesCheckerboard,
    );
  }

  @override
  bool operator ==(dynamic other) {
    if (runtimeType != other.runtimeType) return false;
    final AppOptions typedOther = other;
    return theme == typedOther.theme &&
        textScaleFactor == typedOther.textScaleFactor &&
        textDirection == typedOther.textDirection &&
        platform == typedOther.platform &&
        showPerformanceOverlay == typedOther.showPerformanceOverlay &&
        showRasterCacheImagesCheckerboard ==
            typedOther.showRasterCacheImagesCheckerboard &&
        showOffscreenLayersCheckerboard ==
            typedOther.showRasterCacheImagesCheckerboard;
  }

  @override
  int get hashCode => hashValues(
        theme,
        textScaleFactor,
        textDirection,
        timeDilation,
        platform,
        showPerformanceOverlay,
        showRasterCacheImagesCheckerboard,
        showOffscreenLayersCheckerboard,
      );

  @override
  String toString() {
    return '$runtimeType($theme)';
  }
}

const double _kItemHeight = 48.0;
const EdgeInsetsDirectional _kItemPadding =
    EdgeInsetsDirectional.only(start: 56.0);

class _OptionsItem extends StatelessWidget {
  const _OptionsItem({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    final double textScaleFactor = MediaQuery.textScaleFactorOf(context);

    return MergeSemantics(
      child: Container(
        constraints: BoxConstraints(minHeight: _kItemHeight * textScaleFactor),
        padding: _kItemPadding,
        alignment: AlignmentDirectional.centerStart,
        child: DefaultTextStyle(
          style: DefaultTextStyle.of(context).style,
          maxLines: 2,
          overflow: TextOverflow.fade,
          child: IconTheme(
            data: Theme.of(context).primaryIconTheme,
            child: child,
          ),
        ),
      ),
    );
  }
}

class _BooleanItem extends StatelessWidget {
  const _BooleanItem(this.title, this.value, this.onChanged, {this.switchKey});

  final String title;
  final bool value;
  final ValueChanged<bool> onChanged;

  // [switchKey] is used for accessing the switch from driver tests.
  final Key switchKey;

  @override
  Widget build(BuildContext context) {
    final bool isDark = Theme.of(context).brightness == Brightness.dark;
    return _OptionsItem(
      child: Row(
        children: [
          Expanded(child: Text(title)),
          Switch(
            key: switchKey,
            value: value,
            onChanged: onChanged,
            activeColor: const Color(0xFF39CEFD),
            activeTrackColor: isDark ? Colors.white30 : Colors.black26,
          ),
        ],
      ),
    );
  }
}

class ActionItem extends StatelessWidget {
  const ActionItem(this.text, this.onTap);

  final String text;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return _OptionsItem(
      child: _FlatButton(
        onPressed: onTap,
        child: Text(
          text,
          style: DefaultTextStyle.of(context).style.copyWith(fontSize: 16),
        ),
      ),
    );
  }
}

class _FlatButton extends StatelessWidget {
  const _FlatButton({Key key, this.onPressed, this.child}) : super(key: key);

  final VoidCallback onPressed;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return FlatButton(
      padding: EdgeInsets.zero,
      onPressed: onPressed,
      child: DefaultTextStyle(
        style: Theme.of(context).primaryTextTheme.subhead,
        child: child,
      ),
    );
  }
}

class _Heading extends StatelessWidget {
  _Heading(this.text);

  final String text;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    return _OptionsItem(
      child: DefaultTextStyle(
        style: theme.textTheme.body1.copyWith(
          fontFamily: 'GoogleSans',
          color: theme.accentColor,
        ),
        child: Semantics(
          child: Text(text),
          header: true,
        ),
      ),
    );
  }
}

class _ThemeItem extends StatelessWidget {
  const _ThemeItem(this.options, this.onOptionsChanged);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;

  @override
  Widget build(BuildContext context) {
    return _BooleanItem(
      'Dark Theme',
      options.theme == darkTheme,
      (bool value) {
        onOptionsChanged(
          options.copyWith(
            theme: value ? darkTheme : lightTheme,
          ),
        );
      },
      switchKey: const Key('dark_theme'),
    );
  }
}

class _TextScaleFactorItem extends StatelessWidget {
  const _TextScaleFactorItem(this.options, this.onOptionsChanged);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;

  @override
  Widget build(BuildContext context) {
    return _OptionsItem(
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('Text size'),
                Text(
                  '${options.textScaleFactor.label}',
                ),
              ],
            ),
          ),
          PopupMenuButton<AppTextScaleValue>(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            icon: Icon(
              Icons.arrow_drop_down,
              color: Theme.of(context).iconTheme.color,
            ),
            itemBuilder: (BuildContext context) {
              return kAllTextScaleValues.map<PopupMenuItem<AppTextScaleValue>>(
                  (AppTextScaleValue scaleValue) {
                return PopupMenuItem<AppTextScaleValue>(
                  value: scaleValue,
                  child: Text(scaleValue.label),
                );
              }).toList();
            },
            onSelected: (AppTextScaleValue scaleValue) {
              onOptionsChanged(
                options.copyWith(textScaleFactor: scaleValue),
              );
            },
          ),
        ],
      ),
    );
  }
}

class _TextDirectionItem extends StatelessWidget {
  const _TextDirectionItem(this.options, this.onOptionsChanged);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;

  @override
  Widget build(BuildContext context) {
    return _BooleanItem(
      'Force RTL',
      options.textDirection == TextDirection.rtl,
      (bool value) {
        onOptionsChanged(
          options.copyWith(
            textDirection: value ? TextDirection.rtl : TextDirection.ltr,
          ),
        );
      },
      switchKey: const Key('text_direction'),
    );
  }
}

class _TimeDilationItem extends StatelessWidget {
  const _TimeDilationItem(this.options, this.onOptionsChanged);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;

  @override
  Widget build(BuildContext context) {
    return _BooleanItem(
      'Slow motion',
      options.timeDilation != 1.0,
      (bool value) {
        onOptionsChanged(
          options.copyWith(
            timeDilation: value ? 20.0 : 1.0,
          ),
        );
      },
      switchKey: const Key('slow_motion'),
    );
  }
}

class _PlatformItem extends StatelessWidget {
  const _PlatformItem(this.options, this.onOptionsChanged);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;

  String _platformLabel(TargetPlatform platform) {
    switch (platform) {
      case TargetPlatform.android:
        return 'Mountain View';
      case TargetPlatform.fuchsia:
        return 'Fuchsia';
      case TargetPlatform.iOS:
        return 'Cupertino';
    }
    assert(false);
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return _OptionsItem(
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('Platform mechanics'),
                Text(
                  '${_platformLabel(options.platform)}',
                ),
              ],
            ),
          ),
          PopupMenuButton<TargetPlatform>(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            icon: Icon(
              Icons.arrow_drop_down,
              color: Theme.of(context).iconTheme.color,
            ),
            itemBuilder: (BuildContext context) {
              return TargetPlatform.values.map((TargetPlatform platform) {
                return PopupMenuItem<TargetPlatform>(
                  value: platform,
                  child: Text(_platformLabel(platform)),
                );
              }).toList();
            },
            onSelected: (TargetPlatform platform) {
              onOptionsChanged(
                options.copyWith(platform: platform),
              );
            },
          ),
        ],
      ),
    );
  }
}

class AppOptionsPage extends StatelessWidget {
  const AppOptionsPage({
    Key key,
    @required this.options,
    @required this.onOptionsChanged,
    this.actionTitle,
    this.actions,
  }) : super(key: key);

  final AppOptions options;
  final ValueChanged<AppOptions> onOptionsChanged;
  final String actionTitle;
  final List<ActionItem> actions;

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.only(bottom: 124.0),
      children: [
        _Heading('Display'),
        _ThemeItem(options, onOptionsChanged),
        _TextScaleFactorItem(options, onOptionsChanged),
        _TextDirectionItem(options, onOptionsChanged),
        _TimeDilationItem(options, onOptionsChanged),
        const Divider(),
        _Heading('Platform mechanics'),
        _PlatformItem(options, onOptionsChanged),
        const Divider(),
        _Heading('Proxy'),
        ActionItem(_proxy(), _proxyDialog(context))
      ]
        ..addAll(_buildDiagnosticItems())
        ..addAll(_buildActions()),
    );
  }

  List<Widget> _buildDiagnosticItems() {
    // Boolean showFoo options with a value of null: don't display
    // the showFoo option at all.
    if (null == options.showOffscreenLayersCheckerboard ??
        options.showRasterCacheImagesCheckerboard ??
        options.showPerformanceOverlay) return const [];

    final List<Widget> items = [
      const Divider(),
      _Heading('Diagnostics'),
    ];

    if (options.showOffscreenLayersCheckerboard != null) {
      items.add(
        _BooleanItem('Highlight offscreen layers',
            options.showOffscreenLayersCheckerboard, (bool value) {
          onOptionsChanged(
              options.copyWith(showOffscreenLayersCheckerboard: value));
        }),
      );
    }
    if (options.showRasterCacheImagesCheckerboard != null) {
      items.add(
        _BooleanItem(
          'Highlight raster cache images',
          options.showRasterCacheImagesCheckerboard,
          (bool value) {
            onOptionsChanged(
                options.copyWith(showRasterCacheImagesCheckerboard: value));
          },
        ),
      );
    }
    if (options.showPerformanceOverlay != null) {
      items.add(
        _BooleanItem(
          'Show performance overlay',
          options.showPerformanceOverlay,
          (bool value) {
            onOptionsChanged(options.copyWith(showPerformanceOverlay: value));
          },
        ),
      );
    }
    return items;
  }

  List<Widget> _buildActions() {
    if (actionTitle == null || actions == null || actions.isEmpty) {
      return [];
    }
    return [
      const Divider(),
      _Heading(actionTitle),
    ]..addAll(actions);
  }

  /// "PROXY host:port; PROXY host2:port2; DIRECT"
  String _proxy() {
    if (options.proxy == null || options.proxy.isEmpty) {
      return 'DIRECT';
    }
    return options.proxy;
  }

  VoidCallback _proxyDialog(BuildContext context) {
    return () {
      TextEditingController textController =
          TextEditingController(text: _proxy());
      showDialog(
          context: context,
          builder: (context) {
            return AlertDialog(
              title: Text('Proxy'),
              content: TextField(
                controller: textController,
                decoration: InputDecoration(
                  labelText: 'PROXY host:port; PROXY host2:port2; DIRECT',
                  border: OutlineInputBorder(),
                ),
              ),
              actions: [
                FlatButton(
                  child: Text('CANCEL'),
                  onPressed: () {
                    Navigator.pop(context);
                  },
                ),
                FlatButton(
                  child: Text('OK'),
                  onPressed: () {
                    Navigator.pop(context);
                    onOptionsChanged(
                        options.copyWith(proxy: textController.text));
                  },
                ),
              ],
            );
          });
    };
  }
}
